﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace KontoraME
{
    public class WorldMap
    {
        //Danh sách các stage
        private List<Stage> listStages;
        //Danh sách các tile
        public List<Bitmap> listTiles;
        //Danh sách các đối tượng game
        private List<GameObject> listObjects;
        //Danh sách các body
        private List<Body> listBodies;
        //Tổng sô lượng tiles
        public int tileNum;
        //ID lớn nhất của Object
        public int MaxID = 0;

        internal List<GameObject> ListObjects
        {
            get { return listObjects; }
            set { listObjects = value; }
        }
        public List<Stage> ListStages
        {
            get { return listStages; }
            set { listStages = value; }
        }
        public List<Body> ListBodies
        {
            get { return listBodies; }
            set { listBodies = value; }
        }
        public WorldMap()
        {
            listTiles = new List<Bitmap>();
            listStages = new List<Stage>();
            listObjects = new List<GameObject>();
            listBodies = new List<Body>();
            tileNum = 0;
        }
        public void saveMap(string dir, Node root_node, Node collision_node)
        {
            //Tính toán width height của hình các tiles
            int height = (1 + this.listTiles.Count / GlobalConstants.TILE_SIZE) * GlobalConstants.TILE_SIZE;
            int index = listTiles.Count;
            int afterCol = index / Int16.Parse(((int)Math.Sqrt(index)).ToString());
            int afterRow = index % afterCol == 0 ? index / afterCol: index / afterCol + 1;
            Bitmap set = new Bitmap(afterCol * GlobalConstants.TILE_SIZE, afterRow * GlobalConstants.TILE_SIZE);
            set.SetResolution(72.0F, 72.0F);
            //Vẽ hình tiles vào bitmap
            foreach (Bitmap bm in this.listTiles)
            {
                int i = this.listTiles.IndexOf(bm) % afterCol;
                int j = this.listTiles.IndexOf(bm) / afterCol;

                using (Graphics g = Graphics.FromImage(set))
                {
                    g.InterpolationMode = InterpolationMode.NearestNeighbor;
                    g.DrawImage(bm, i * GlobalConstants.TILE_SIZE, j * GlobalConstants.TILE_SIZE, GlobalConstants.TILE_SIZE, GlobalConstants.TILE_SIZE);
                }
            }
            //Lưu hình tile
            string name = dir.Split('.')[0];
            string path = name + ".bmp";
            using (MemoryStream memory = new MemoryStream())
            {
                using (FileStream fs = new FileStream(path, FileMode.Create, FileAccess.ReadWrite))
                {
                    set.Save(memory, ImageFormat.Bmp);
                    byte[] bytes = memory.ToArray();
                    fs.Write(bytes, 0, bytes.Length);
                }
            }
            //Lưu file
            using (FileStream fs = new FileStream(name + ".txt", FileMode.Create, FileAccess.Write))
            {
                StreamWriter sw = new StreamWriter(fs);
                sw.AutoFlush = true;
                //Phần HEADER chứa các thông tin về Map
                //Map info chứa số lượng Stage, số lượng Tile, kích thước của Tile, số lượng Object và số lượng Body
                sw.WriteLine("HEADER");
                sw.WriteLine("Map Info");
                sw.WriteLine("Stage_Num" + "\t" + "Tiles_Num" + "\t" + "Tile_Size" + "\t" + "Object_Num" + "\t" + "Body_Num");
                sw.WriteLine(listStages.Count + "\t" + listTiles.Count + "\t" + GlobalConstants.TILE_SIZE + "\t" 
                    + listObjects.Count + "\t" + listBodies.Count);
                //Stage Info chứa vị trí và tổng số dòng cột của mỗi Stage
                sw.WriteLine("\nState Info");
                sw.WriteLine("Pos_X" + "\t" + "Pos_Y" + "\t" + "Col_Num" + "\t" + "Row_Num");
                foreach (Stage st in this.listStages)
                    sw.WriteLine(st.position.X.ToString() + "\t" + st.position.Y.ToString() + "\t" + st.cols + "\t" + st.rows);
                sw.WriteLine("END_HEADER\n");
                //Phần STATE_SET chứa ma trận tile
                sw.WriteLine("STATE_SET");
                foreach (Stage st in this.listStages)
                {
                    sw.WriteLine("State " + (listStages.IndexOf(st) + 1));
                    for (int i = 0; i < st.rows; i++)
                    {
                        for (int j = 0; j < st.cols; j++)
                            sw.Write(st.matrixTiles[i, j] + "\t");
                        sw.WriteLine();
                    }
                }
                sw.WriteLine("END_STATE_SET\n");
                //Phần Object List chứa danh sách Object
                sw.WriteLine("OBJECT_LIST");
                sw.WriteLine("ID" + "\t\t" + "Type" + "\t\t"+ "Pos_X" + "\t\t" + "Pos_Y" + "\t\t" + "BodyID" + "\t\t" + "Value1" + "\t\t" + "Value2");
                int posX, posY, h, w;
                foreach (GameObject o in this.listObjects)
                {
                    if (o.Type != GlobalConstants.TILE_ID) 
                    {
                        posX = o.Point.X + o.Size.Width / 2;
                        posY = o.Point.Y + o.Size.Height / 2;
                        sw.WriteLine(o.Id + "\t\t" + o.Type + "\t\t" +
                                 posX.ToString() + "\t\t" + posY.ToString()
                                 + "\t\t" + o.Body.BodyId.ToString()
                                 + "\t\t" + o.Value1 + "\t\t" + o.Value2);
                    }
                }
                sw.WriteLine("END_OBJECT_LIST\n");
                //Phần Body List chứa danh sách Body
                //còn vài cái chưa xử lý như vùng sinh và vùng di chuyển
                sw.WriteLine("BODY_LIST");
                sw.WriteLine("ID" + "\t\t" + "Type" + "\t\t" + "Pos_X" + "\t\t" + "Pos_Y" + "\t\t" + "Width" + "\t\t" + "Height");
                foreach (Body b in this.listBodies)
                {
                    posY = listStages[0].rows * GlobalConstants.TILE_SIZE - b.Point.Y;
                    switch (b.BodyType)
                    {
                        case GlobalConstants.WATER_BODY_ID:
                            posY = posY - 12;
                            h = b.Size.Height - 12;
                            sw.WriteLine(b.BodyId + "\t\t" + b.BodyType + "\t\t" +
                                 b.Point.X.ToString() + "\t\t" + posY.ToString() + "\t\t"
                                 + b.Size.Width.ToString() + "\t\t" + h.ToString());
                            break;
                        case GlobalConstants.GRASS_BODY_ID:
                        case GlobalConstants.GRASS1_BODY_ID:
                        case GlobalConstants.BRIDGE_ID:
                            posY = posY - 12;
                            h = b.Size.Height - 12;
                            sw.WriteLine(b.BodyId + "\t\t" + b.BodyType + "\t\t" +
                                 b.Point.X.ToString() + "\t\t" + posY.ToString() + "\t\t"
                                 + b.Size.Width.ToString() + "\t\t" + h.ToString());
                            break;
                        case GlobalConstants.BOSS1_ID:
                            posX = b.Point.X - 416;
                            posY = GlobalConstants.VP_HEIGHT;
                            w = GlobalConstants.VP_WIDTH;
                            h = GlobalConstants.VP_HEIGHT;
                            sw.WriteLine(b.BodyId + "\t\t" + b.BodyType + "\t\t" +
                                 posX.ToString() + "\t\t" + posY.ToString() + "\t\t"
                                 + w.ToString() + "\t\t" + h.ToString());
                            break;
                        case GlobalConstants.BOSS5_DOOR_ID:
                            posX = b.Point.X - 320;
                            posY = GlobalConstants.VP_HEIGHT;
                            w = GlobalConstants.VP_WIDTH;
                            h = GlobalConstants.VP_HEIGHT;
                            sw.WriteLine(b.BodyId + "\t\t" + b.BodyType + "\t\t" +
                                 posX.ToString() + "\t\t" + posY.ToString() + "\t\t"
                                 + w.ToString() + "\t\t" + h.ToString());
                            break;
                        case GlobalConstants.BLOCKHOUSE_ID:
                            posX = b.Point.X + 32 - 14;
                            posY = posY - 32 + 14;
                            w = 28;
                            h = 28;
                            sw.WriteLine(b.BodyId + "\t\t" + b.BodyType + "\t\t" +
                                 posX.ToString() + "\t\t" + posY.ToString() + "\t\t"
                                 + w.ToString() + "\t\t" + h.ToString());
                            break;
                        default:
                            sw.WriteLine(b.BodyId + "\t\t" + b.BodyType + "\t\t" +
                                 b.Point.X.ToString() + "\t\t" + posY.ToString() + "\t\t"
                                 + b.Size.Width.ToString() + "\t\t" + b.Size.Height.ToString());
                            break;
                    }
                }
                sw.WriteLine("END_BODY_LIST\n");
                sw.WriteLine("QUAD_TREE_DRAW");
                sw.WriteLine("ID" + "\t\t" + "Pos_X" + "\t\t" + "Pos_Y" + "\t\t" + 
                    "Width" + "\t\t" + "Height" + "\t\t" + "Node_Num" + "\t" + "Objs_Num" + "\t" + "List_Obj");
                root_node.Save(sw);
                sw.WriteLine("END_QUAD_TREE_DRAW");
                sw.WriteLine("QUAD_TREE_COLLISION");
                sw.WriteLine("ID" + "\t\t" + "Pos_X" + "\t\t" + "Pos_Y" + "\t\t" +
                    "Width" + "\t\t" + "Height" + "\t\t" + "Node_Num" + "\t" + "Bodies_Num" + "\t" + "List_Body");
                collision_node.SaveBody(sw);
                sw.WriteLine("END_QUAD_TREE_COLLISION");
                sw.Close();
                fs.Close();
            }
            listBodies.Clear();
            listObjects.Clear();
            ListStages.Clear();
            listTiles.Clear();
            this.loadMap(dir, root_node);
        }
        public void loadMap(string dir, Node root_node)
        {
            using (FileStream fs = new FileStream(dir, FileMode.Open, FileAccess.Read))
            {
                StreamReader rd = new StreamReader(fs);
                //Load HEADER
                string rs = rd.ReadLine();
                if (rs != "HEADER")
                    throw new Exception("Invalid Map");

                rd.ReadLine(); //Map Info
                rd.ReadLine(); //"State_Num" + "\t" + "Tiles_Num" + "\t" + "Tiles_pRow" + "\t" + "Object_Num"
                string[] s = rd.ReadLine().Split('\t');
                int state_Num = int.Parse(s[0]);
                int tiles_Num = int.Parse(s[1]);
                int objec_Num = int.Parse(s[3]);
                int body_num = int.Parse(s[4]);

                rd.ReadLine(); //State Info
                rd.ReadLine(); //Space
                rd.ReadLine(); //"Pos_X" + "\t\t" + "Pos_Y" + "\t\t" + "Col_Num" + "\t\t" + "Row_Num"

                listStages.Clear();
                int axisY = 0;
                for (int i = 0; i < state_Num; i++)
                {
                    string str = rd.ReadLine();
                    str = str.Replace("\t\t\t", "\t");
                    str = str.Replace("\t\t", "\t");
                    s = str.Split('\t');
                    Stage st = new Stage();
                    int x = int.Parse(s[0]);
                    int y = int.Parse(s[1]);
                    st.position = new Point(x, y);
                    st.cols = int.Parse(s[2]);
                    st.rows = int.Parse(s[3]);
                    axisY = st.rows * GlobalConstants.TILE_SIZE;
                    st.matrixTiles = new int[st.rows, st.cols];
                    st.matrixBody = new int[st.rows, st.cols];
                    listStages.Add(st);
                }
                objec_Num = objec_Num - (this.ListStages[0].cols * this.ListStages[0].rows);
                rd.ReadLine(); //END_HEADER
                rd.ReadLine(); //Space
                rd.ReadLine(); //STATE_SET
                foreach (Stage st in this.listStages)
                {
                    rd.ReadLine(); //State Num
                    for (int i = 0; i < st.rows; i++)
                    {
                        s = rd.ReadLine().Split('\t');
                        for (int j = 0; j < st.cols; j++)
                            st.matrixTiles[i, j] = int.Parse(s[j]);
                    }
                }
                rd.ReadLine(); //END_STATE_SET
                rd.ReadLine(); //Space
                rd.ReadLine(); //OBJECT_LIST
                rd.ReadLine(); //"ID" + "\t\t" + "Type" + "\t" + "Pos_X" + "\t\t" + "Pos_Y" + "\t\t" + BodyID

                for (int i = 0; i < objec_Num; i++)
                {
                    string str = rd.ReadLine();
                    str = str.Replace("\t\t\t", "\t");
                    str = str.Replace("\t\t", "\t");
                    s = str.Split('\t');
                    int id = int.Parse(s[0]);
                    int type = int.Parse(s[1]);
                    int x = int.Parse(s[2]);
                    int y = int.Parse(s[3]);
                    int bodyId = int.Parse(s[4]);
                    int value1 = int.Parse(s[5]);
                    int value2 = int.Parse(s[6]);
                    GameObject o = new GameObject(id, type, new Point(x, y), value1, value2);
                    o.Point = new Point(o.Point.X - o.Size.Width / 2, o.Point.Y - o.Size.Height / 2 );
                    o.Body = new Body(bodyId, 
                        new Point(x, y), 
                        new Size(o.Size.Width, o.Size.Height), 
                        o.Type);
                    this.listObjects.Add(o);
                }

                rd.ReadLine(); //END_OBJECT_LIST
                rd.ReadLine(); //Space
                rd.ReadLine(); //BODY_LIST
                rd.ReadLine(); //"ID" + "\t\t" + "Type" + "\t" + "Pos_X" + "\t\t" + "Pos_Y" 
                for (int i = 0; i < body_num; i++)
                {
                    string str = rd.ReadLine();
                    str = str.Replace("\t\t\t", "\t");
                    str = str.Replace("\t\t", "\t");
                    s = str.Split('\t');
                    int id = int.Parse(s[0]);
                    int type = int.Parse(s[1]);
                    int x = int.Parse(s[2]);
                    int y = int.Parse(s[3]);
                    int w = int.Parse(s[4]);
                    int h = int.Parse(s[5]);
                    switch (type)
                    {
                        case GlobalConstants.WATER_BODY_ID:
                        case GlobalConstants.GRASS_BODY_ID:
                        case GlobalConstants.GRASS1_BODY_ID:
                        case GlobalConstants.BRIDGE_ID:
                            y = y + 12;
                            h = h + 12;
                            break;
                    }
                    Body b = new Body(id, new Point(x,axisY - y), new Size(w, h), type);
                    this.listBodies.Add(b);
                    if (MaxID < id)
                        MaxID = id;
                }
                rd.ReadLine(); //END_BODY_LIST

                rd.Close();
                fs.Close();

                string tiles_dir = dir.Split('.')[0] + ".bmp";
                Bitmap background = new Bitmap(tiles_dir);
                Bitmap bm = new Bitmap(background.Width, background.Height);
                using (Graphics g = Graphics.FromImage(bm))
                {
                    g.InterpolationMode = InterpolationMode.NearestNeighbor;
                    g.DrawImage(background, 0, 0, background.Width,background.Height);
                    background = bm;
                }
                int bg_col = background.Width / GlobalConstants.TILE_SIZE;

                for (int k = 0; k < tiles_Num; k++)
                {
                    Rectangle r2 = new 
                        Rectangle(k % bg_col * GlobalConstants.TILE_SIZE, k / bg_col * GlobalConstants.TILE_SIZE, GlobalConstants.TILE_SIZE, GlobalConstants.TILE_SIZE);
                    Bitmap b = background.Clone(r2, background.PixelFormat);
                    listTiles.Add(b);
                }
            }
        }
    }

    public class Stage
    {
        public Point position;
        public int cols;
        public int rows;
        public int[,] matrixTiles;
        public int[,] matrixBody;
    }
}
